iT邦幫忙

2026 iThome 鐵人賽

DAY 6
0
Build on Google AI

《30天打造喵語日誌:Gemini API × Vibe Coding 實戰》系列 第 6

Day 06:【前端實作】Vite + React 骨架建置與 Gemini API 結構化串接全紀錄

  • 分享至 

  • xImage
  •  

從 AI Studio 走向前端工程

前幾天我們都在 Google AI Studio 裡測試《喵語日誌》的功能:先設定貓咪 Persona,再加入多模態分析,最後使用 Response Schema 讓 Gemini 穩定輸出 JSON。

不過,AI Studio 裡測試成功,只代表概念驗證完成。真正要做成一個可以操作的應用程式,還是要把這些設定搬進前端專案裡。

所以今天要正式進入實作階段,完成這條完整流程:

使用者輸入近況 → 前端呼叫 Gemini API → Gemini 回傳結構化 JSON → React 即時渲染貓咪日誌卡片

接下來就從建立 Vite + React 專案開始吧!

建立 Vite + React 專案

首先使用 Vite 建立一個 React + TypeScript 專案:

npm create vite@latest cat-diary -- --template react-ts
cd cat-diary
npm install
npm install @google/genai
npm run dev

啟動完成後,瀏覽器打開終端機顯示的網址,就可以看到 Vite 的初始畫面。

接著,我在 VS code 中把 src 資料夾整理成以下結構:

src/
├── components/
│   └── DiaryCard.tsx
├── services/
│   └── geminiService.ts
├── types/
│   └── diary.ts
├── App.tsx
└── main.tsx

這樣分工之後,每個檔案都有比較清楚的責任:

  • components:負責畫面元件
  • services:負責 API 呼叫
  • types:負責 TypeScript 型別定義
  • App.tsx:負責頁面狀態與互動流程

環境變數與 API Key 管理

為了避免直接把 API Key 寫在程式碼中,我們先使用 Vite 的環境變數機制管理本機設定。

在專案根目錄建立 .env.local 檔案:

VITE_GEMINI_API_KEY=your_gemini_api_key_here

接著確認 .gitignore 中包含:

.env
.env.*
!.env.example

這樣可以避免不小心把本機環境設定檔推送到 Git 儲存庫。Vite 也建議將本機使用的 .env.local 類型檔案加入 Git 忽略清單。

在前端程式中,可以透過以下方式讀取:

const apiKey = import.meta.env.VITE_GEMINI_API_KEY;

這次先使用環境變數完成本機練習,避免將金鑰直接寫死在程式碼裡。
不過要特別注意,因為 VITE_ 開頭的變數會被打包到瀏覽器端,所以這種方式適合學習和 Demo;正式上線時,仍應將 API 呼叫移到後端。

型別定義與 UI 卡片

1. 建立 TypeScript Interface

Day 05 的 JSON Schema 有三個欄位,因此我們可以在 src/types/diary.ts 中建立對應的型別:

// src/types/diary.ts
export interface CatDiaryResponse {
  mood_score: number;
  lifestyle_label: string;
  cat_response: string;
}

這樣做的好處是,當 API 回傳資料時,TypeScript 可以幫我們檢查欄位名稱和資料型別,減少前端寫錯的機會。

2. 建立 DiaryCard 元件

接著建立 src/components/DiaryCard.tsx

import type { CatDiaryResponse } from "../types/diary";

interface DiaryCardProps {
  data: CatDiaryResponse;
}

export function DiaryCard({ data }: DiaryCardProps) {
  return (
    <div className="diary-card">
      <div className="card-header">
        {/* 顯示生活場景標籤 */}
        <span>🏷️ {data.lifestyle_label}</span>
        {/* 顯示情緒指數 */}
        <span>情緒指數:{data.mood_score} / 10</span>
      </div>

      {/* 顯示貓咪陪伴文字 */}
      <div className="card-content">
        🐾 {data.cat_response}
      </div>
    </div>
  );
}

這個元件會接收一個 CatDiaryResponse 物件,然後把三個欄位分別顯示成:

  • 生活標籤
  • 情緒指數
  • 貓咪陪伴文字

串接 Gemini API

1. 建立 API Service

src/services/geminiService.ts 中封裝 Gemini API:

import { GoogleGenAI, Type } from "@google/genai";
import type { CatDiaryResponse } from "../types/diary";

const apiKey = import.meta.env.VITE_GEMINI_API_KEY;
const ai = new GoogleGenAI({ apiKey });

export async function analyzeDiary(userText: string): Promise<CatDiaryResponse> {
  const response = await ai.models.generateContent({
    model: "gemini-3-flash-preview",
    contents: userText,
    config: {
      systemInstruction: "你是一隻名為「喵喵」的貼心寵物貓...",
      responseMimeType: "application/json",
      // 強制要求 Gemini 輸出符合結構的 JSON
      responseSchema: {
        type: Type.OBJECT,
        properties: {
          mood_score: { type: Type.INTEGER, description: "1 到 10 的情緒指數" },
          lifestyle_label: { type: Type.STRING, description: "生活場景標籤" },
          cat_response: { type: Type.STRING, description: "貓咪陪伴回應" },
        },
        required: ["mood_score", "lifestyle_label", "cat_response"],
      },
    },
  });

  const jsonText = response.text;
  if (!jsonText) throw new Error("未取得 Gemini 回應");

  return JSON.parse(jsonText) as CatDiaryResponse;
}

這裡最重要的設定是:

responseMimeType: "application/json"

以及:

responseSchema: {
  ...
}

前者要求模型使用 JSON 格式回應,後者則定義 JSON 必須包含哪些欄位和型別。Gemini 官方文件也示範了使用 responseSchema 搭配 Type.OBJECTType.STRINGType.INTEGER 來限制輸出結構。

這次範例使用的模型 ID 是:

"gemini-3-flash-preview"

Gemini 模型名稱會隨版本和平台更新,實作時最好以 Google AI Studio 或官方模型清單中顯示的正式 ID 為準,不要自行猜測模型名稱。gemini-3-flash-preview 是官方列出的模型代碼。

2. 在 App.tsx 整合互動流程

接著修改 src/App.tsx

import { useState } from "react";
import { DiaryCard } from "./components/DiaryCard";
import { analyzeDiary } from "./services/geminiService";
import type { CatDiaryResponse } from "./types/diary";

function App() {
  const [inputText, setInputText] = useState("");
  const [loading, setLoading] = useState(false);
  const [diaryData, setDiaryData] = useState<CatDiaryResponse | null>(null);
  const [errorMessage, setErrorMessage] = useState("");

  // 表單送出處理
  async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
    event.preventDefault();
    if (!inputText.trim()) return;

    setLoading(true);
    setErrorMessage("");

    try {
      const result = await analyzeDiary(inputText);
      setDiaryData(result); // 儲存 API 回傳的結構化資料
    } catch (error) {
      setErrorMessage("喵喵暫時沒聽懂,請稍後再試!");
    } finally {
      setLoading(false);
    }
  }

  return (
    <main>
      <h1>🐾 喵語日誌</h1>
      <form onSubmit={handleSubmit}>
        <textarea value={inputText} onChange={(e) => setInputText(e.target.value)} />
        <button type="submit" disabled={loading}>
          {loading ? "喵喵正在思考中..." : "送出給喵喵"}
        </button>
      </form>

      {/* API 分析成功後渲染卡片 */}
      {diaryData && <DiaryCard data={diaryData} />}
    </main>
  );
}

export default App;

這裡使用三個 useState

  • inputText:保存文字輸入
  • loading:控制送出後的載入狀態
  • diaryData:保存 Gemini 回傳的 JSON 資料
  • errorMessage:顯示 API 失敗時的提示

整個互動流程就完成了:

輸入文字
↓
點擊送出
↓
loading = true
↓
呼叫 analyzeDiary()
↓
取得 JSON
↓
更新 diaryData
↓
DiaryCard 重新渲染

實作踩坑、成果

這次串接過程中,我遇到兩個問題。

TypeScript 匯入型別出現紅字

從純 .ts 檔案匯入 Interface 時,建議使用:

import type { CatDiaryResponse } from "../types/diary";

因為 Interface 只存在於編譯階段,並不是執行時真正需要載入的 JavaScript 物件。加上 type 後,TypeScript 對這類匯入的判斷會更清楚。
如果 VS Code 還是顯示奇怪的紅字,也可以執行:

TypeScript: Restart TS Server

有時候只是編輯器的型別快取還沒有更新。

Model ID 報錯 404

如果 API 回傳 404 Not Found,很可能是模型名稱不存在、拼錯,或目前的 API 版本不支援。
例如:

model: "gemini-2.5-flash"

不一定在所有時期或環境都能直接使用。實作時要以 AI Studio 或官方模型清單中的模型 ID 為準。

實際測試成果

這次輸入的內容是:

今天沒發生什麼特別的事,但是完成前端專案骨架建置,眼睛有點酸,只想躺在床上發呆。

點擊送出後,Gemini 成功回傳結構化資料,前端也順利渲染出日誌卡片:
https://ithelp.ithome.com.tw/upload/images/20260807/20178708waFgGAyx0k.png

結語

從前幾天的 AI Studio 測試,到今天正式讓會說貓語的 AI 躍上網頁,我們順利完成了專案骨架、型別對接、結構化輸出與 API 串接!看著文字輸入後即時變成貓咪卡片,真的非常有成就感。

💡 提醒:本篇的前端直連方式適合學習與 Demo,若要正式上線,建議將 API 呼叫移至後端或 Cloud Functions,避免金鑰暴露於瀏覽器中。

明天將為第一週進行總整理!我們會彙整這幾天從 Prompt 到 API 串接的成果,並用 AI 工具產出《喵語日誌》三大核心畫面(對話、月曆、成就卡)的 Wireframe 原型,準備迎接下一階段的 Vibe Coding 實戰!喵~


上一篇
Day 05:【Structured Outputs】定義 Response Schema,零失敗率的數據對接
下一篇
Day 07:【週總結】第一週 AI 基礎建設小結:利用 AI 繪製《喵語日誌》三大核心畫面 Wireframe
系列文
《30天打造喵語日誌:Gemini API × Vibe Coding 實戰》20
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言